/* Copyright 2009 Sony Mathew, Minneapolis MN. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at  http://www.apache.org/licenses/LICENSE-2.0  Unless required by applicable law or agreed to in writing, software  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */
package com.xsm.gwt.widgets.client;

import java.util.Collection;
import java.util.Collections;

import com.google.gwt.user.client.ui.CellPanel;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * GWT makes it easy to create and listen to the widgets that the user
 *  interacts with (like buttons, textboxes, etc.) but when designing a UI
 *  one can spend a considerable amount of time in layout of these interactive
 *  widgets - positioning and spacing them just right. GWT does provide many 
 *  layout Panels but their usage can create verbose code that is hard 
 *  to modify or grasp after the layout is written.   
 *  <br><br>
 *  AxisPanel provides a simple concise way to layout a complex UI using short
 *  continous expressions that place your widgets in either the X or Y axis 
 *  allowing you to recursively create a layout of any degree of complexity.
 *  Every intermediate X or Y axis panel is stylable and customizable 
 *  on the fly within the same expression. AxisPanel can shrink a page length 
 *  of Java code to one easy-to-read expression which enables easy 
 *  experimentation until your layout is achieved. Allows you to focus your 
 *  time/energy on the interesting dynamics that GWT handles so beautifully 
 *  and  less on the not-so-interesting layout details.  AxisPanel is also more 
 *  concise than its equivalent HTML or Declarative XML type layouts.  
 *  <br><br>
 * <b>Sample Usage:</b>
 *  <code><pre>
 *  AxisPanel p = new AxisPanel();      
 *  p.id("search").size(800,600).align(0,0).space(10).border(1)
 *      .y().id("options").size(0.4,1.0).space(10).align(-1,0)
 *          .x("Enter your Search here:").q()
 *          .x(searchBox,searchButton).size(-10,-10).border(1).q()
 *          .q()
 *      .y().id("results").size(-10,-10).align(0,0).space(20).border(1)
 *          .x().css("header").size(10,200).align(0,0).border(1)
 *             .x(textbox2, label2).q()
 *             .y(textbox3, button3).q()
 *             .q()
 *          .x().css("help").border(1).size(100,20)
 *              .x(helpLabel, helpSearchBox, helpHyperLink).q()
 *              .y(textbox5, button5).q()
 *              .q()
 *          .x(searchResultsTable).css("results").size("100%","100%").q()              
 *          .y(textbox6,button6).q()
 *          .q()
 *  </code></pre>
 *  <b>How to Read:</b><br>
 *  <ul>
 *  <li>
 *  AxisPanel<br>
 *      The root panel and the starting target to which all additions go.
 *      The AxisPanel is a VerticalPanel (Y Axis) to start with.
 *  </li>
 *  <li>
 *  .y(Widget...)<br>
 *      Adds a VerticalPanel (Y Axis) to current target panel 
 *      with the given Widgets. Switches target to this new Y Axis Panel and all 
 *      further actions are directed to this Panel.  The added widgets will 
 *      line up vertically (Y Axis) within this new Panel.
 *   </li>
 *   <li>
 *  .x(Widget...)<br> 
 *      Adds a HorizontalPanel (X Axis) to current target panel 
 *      with the given Widgets. Switches target to this new X Axis Panel and all 
 *      further actions are directed to this Panel.  The added widgets will 
 *      line up horizontally (X Axis) within this new Panel.
 *      .x(String) is convenience shortcut for .x(new Label(String)).
 *  </li>
 *  <li>
 *  .id(String), .css(String), size(int,int), border(int), space(int) etc.<br/> 
 *      Set element id, add css class, size, borderWidth, spacing etc. 
 *          to the target widget.
 *      <br>
 *      id() ultimately gets prefixed with parent's id separated by '-' .
 *      <br> 
 *      size() is overloaded e.g. negative and double values are 
 *          relative to parent's size. 
 *   </li>
 *   <li>
 *  .q()<br> 
 *      Quit adding to the current target Panel.
 *      Switches target back to parent panel in use before this panel.
 *      Every x() or y() axis addtions should end with a q().  
 *      But You do NOT close the root AxisPanel with a q() since The
 *          expression can be restarted on another line/loc.
 *  </li>
 *  <li>
 *  indentation<br>
 *      All children are indented.
 *  </li>
 *  </ul>
 * <br> 
 * TODO: Save child widgets and styles for later commit on q() so that 
 *  align() doesn't have to remove/re-add children.
 *  <br>
 * TODO: Use DOM API directly to generate more concise HTML.
 *   
 * @author Sony Mathew
 */
public class AxisPanel extends VerticalPanel implements AxisPanelStyle.Owner {

    /**
     * @author Sony Mathew
     */
    public static class XPanel extends HorizontalPanel implements AxisPanelStyle.Owner {
        
        private AxisPanelStyle style = new AxisPanelStyle(this);
        
        public AxisPanelStyle getAxisPanelStyle() {
            return style;
        }
    }
    
    /**
     * @author Sony Mathew
     */
    public static class YPanel extends VerticalPanel implements AxisPanelStyle.Owner {
        
        private AxisPanelStyle style = new AxisPanelStyle(this);
        
        public AxisPanelStyle getAxisPanelStyle() {
            return style;
        }
    }    
    
    private final AxisPanelStyle style = new AxisPanelStyle(this);
    private CellPanel target = this; 
    

    /**
     * @return AxisPanelStyle: The AxisPanelStyle of this Panel.
     */    
    public AxisPanelStyle getAxisPanelStyle() {
        return style;
    }
    
    /**
     * Adds a VerticalPanel to the current target, this new panel 
     *    will be the new target for all actions until next q().
     *      
     * Also provide 1 or more widgets for immediate addition.
     * 
     * @param ws: The widgets being added to the new Y Axis Panel.
     * @return AxisPanel: Self
     */
    public AxisPanel y(Widget...ws) {
        addTarget(new YPanel());
        addWidgets(ws);
        return this;
    }
        
    /**
     * Convenience for Collection of widgets.
     * 
     * @see #y(Widget[])
     */
    public AxisPanel y(Collection<? extends Widget> ws) {
        if (ws==null) {
            ws = Collections.emptyList(); 
        }
        return y(ws.toArray(new Widget[ws.size()]));
    }
        
    /**
     * Adds a HorizontalPanel to the current target and all new Widgets added 
     *      will now go into this Panel until a q() call. 
     *      
     * @param ws: The widgets being added to the new Y Axis Panel.
     * @return AxisPanel: Self
     */    
    public AxisPanel x(Widget...ws) {
        addTarget(new XPanel());
        addWidgets(ws);        
        return this;
    }
       
    /**
     * Convenience for Collection of widgets.
     * 
     * @see #x(Widget[])
     */
    public AxisPanel x(Collection<? extends Widget> ws) {
        if (ws==null) {
            ws = Collections.emptyList(); 
        }
        return x(ws.toArray(new Widget[ws.size()]));
    }
    
    /**
     * Convenience method. Same as x(new Label(s)).
     */
    public AxisPanel x(String s) {
        return x(new Label(s));
    }
    
    /**
     * Provide a Panel that decorates the current target.
     *      The current target will be added to the given decorating panel.
     *      using its add() method.
     * 
     * @param decorPanel: The Panel to decorate the current target with.
     * @return AxisPanel: Self
     */
    public AxisPanel decor(HasWidgets decorPanel) {
        HasWidgets targetParent = (HasWidgets)target.getParent();
        decorPanel.add(target);
        if (targetParent != null) {
            targetParent.add((Widget)decorPanel);
        }
        return this;        
    }
        
    /**
     * Indicate that the current panel being built is done.
     *  All further actions will target the parent of the current target panel.
     *  
     * Note: Can never close the root panel with a q().  An exception will be
     *  thrown.
     *  
     * @return AxisPanel: Self
     */
    public AxisPanel q() {
        if (target == this) {
            throw new IllegalStateException("Too many q() calls to AxisPanel");
        }        
        setTarget(getTargetParent());
        return this;
    }
        
    /**
     * Adds Widgets to the current target X | Y panel.
     * Can put null widget which is merely ignored.
     *  This is for convenience.
     *      
     * @param ws: The widgets being added.
     * @return AxisPanel: Self
     */    
    public AxisPanel put(Widget...ws) {
        addWidgets(ws);        
        return this;
    }
       
    /**
     * Convenience for Collection of widgets.
     * 
     * @see #x(Widget[])
     */
    public AxisPanel put(Collection<? extends Widget> ws) {
        if (ws==null) {
            ws = Collections.emptyList(); 
        }
        return put(ws.toArray(new Widget[ws.size()]));
    }
    
    /**
     * Convenience for put(new Label(String));
     */    
    public AxisPanel put(String label) {               
        return put(new Label(label));
    }
    
    /**
     * Adds the given widget to the current target and makes this widget the 
     *  new target for all further actions.
     */
    private void addTarget(CellPanel p) {
        target.add(p);
        setTarget(p);
    }
        
    /**
     * Add widgets to the current target panel being built.
     */
    private void addWidgets(Widget...ws) {
        if (ws != null && ws.length != 0) {            
            for(Widget w: ws) {
                if (w != null) {
                    target.add(w);
                }
            }            
        }
    }
    
    private void setTarget(Widget w) {
        if (isTargetable(w)) {
            target = (CellPanel)w;
        }else{
            throw new IllegalStateException("AxisPanel target invalid [" + w + "]");
        }
    }

    
    /**
     * Indicate if the given widget can be set as the target.
     * Currently Only allows an AxisPanel, XPanel or YPanel to be
     *  targetted. 
     */
    private boolean isTargetable(Widget w) {
        return ( (w instanceof AxisPanel) || (w instanceof XPanel) 
            || (w instanceof YPanel) );
    }
    
    /**
     * Obtain the first targetable parent of the current target.
     * 
     * @return : The parent CellPanel.
     * 
     * author Sony Mathew
     */
    private CellPanel getTargetParent() {
        Widget t = target.getParent();
        while(t != null && !isTargetable(t)) {
            t = t.getParent();
        }        
        if (t == null) {
            throw new IllegalStateException("No targetable parent found " +
                    "for current target");
        }
        return (CellPanel)t;
    }
        
    /**
     * author Sony Mathew
     */
    private AxisPanelStyle getTargetStyle() {
        return ((AxisPanelStyle.Owner)target).getAxisPanelStyle();
    }
    
// ------------------------------------------
// Delegate methods to AxisPanelStyle
// ------------------------------------------
        
    /**
     * @see AxisPanelStyle#id(String)
     */
    public AxisPanel id(String id) {
        getTargetStyle().id(id);
        return this;
    }
    
    /**
     * @see AxisPanelStyle#styke(String,String)
     */
    public AxisPanel style(String styleAttrib, String styleValue) {
        getTargetStyle().style(styleAttrib,styleValue);
        return this;
    }
    
    /**
     * @see AxisPanelStyle#css(String)
     */
    public AxisPanel css(String cssName) {
        getTargetStyle().css(cssName);
        return this;
    }

    /**
     * @see AxisPanelStyle#border(int)
     */
    public AxisPanel border(int borderWidth) {
        getTargetStyle().border(borderWidth);
        return this;
    }

    /**
     * Convenience for creating a solid border using styles with the given width and color.
     */
    public AxisPanel styleBorder(int width, String color) {
        style("borderStyle", "solid");
        style("borderColor", color);
        style("borderWidth", ""+width);
        return this;
    }

    /**
     * Convenience for creating a solid border using styles with default color and the given width. 
     */
    public AxisPanel styleBorder(int width) {
        style("borderStyle", "solid");
        style("borderColor", "#BADBEB");
        style("borderWidth", "1");
        return this;
    }
    
    /**
     * @see AxisPanelStyle#size(int, int)
     */
    public AxisPanel size(int pixelWidth, int pixelHeight) {
        getTargetStyle().size(pixelWidth, pixelHeight);        
        return this;
    }

    /**
     * @see AxisPanelStyle#size(double, double)
     */
    public AxisPanel size(double pixelWidthPercent, double pixelHeightPercent) {
        getTargetStyle().size(pixelWidthPercent, pixelHeightPercent);
        return this;
    }

    /**
     * @see AxisPanelStyle#size(String, String)
     */
    public AxisPanel size(String cssWidth, String cssHeight) {
        getTargetStyle().size(cssWidth, cssHeight);
        return this;
    }
    
    /**
     * @see AxisPanelStyle#width(String)
     */
    public AxisPanel width(String cssWidth) {
        getTargetStyle().width(cssWidth);        
        return this;
    }
    
    /**
     * @see AxisPanelStyle#height(String)
     */
    public AxisPanel height(String cssHeight) {
        getTargetStyle().height(cssHeight);
        return this;
    }
    
    /**
     * Shortcut for width("100%") + height("100%")
     * 
     * author Sony Mathew
     */
    public AxisPanel stretch() {
        width("100%");
        height("100%");
        return this;
    }
    /**
     * @see AxisPanelStyle#space(int)
     */
    public AxisPanel space(int pixels) {
        getTargetStyle().space(pixels);
        return this;
    }

    /**
     * @see AxisPanelStyle#align(int, int)
     */
    public AxisPanel align(int x, int y) {
        getTargetStyle().align(x,y);
        return this;
    }
    
}
